#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <ctype.h>

#include <object.h>
#include <line.h>
#include <circle.h>
#include <ellipse.h>
#include <polygon.h>

#include <scene.h>
#include <visual.h>

#include <search.h>

static surface_t *screen = NULL;


typedef struct hsearch_data hashtable_t;
typedef struct _ENTRY
{
  unsigned int used;
  ENTRY entry;
} _ENTRY;

hashtable_t *
hashtable_new( void )
{
  hashtable_t *hash = calloc( 1, sizeof(*hash) );
  hcreate_r( 20, hash );
  return hash;
}

void
hashtable_free( hashtable_t *hash )
{
  _ENTRY *p, *end;

  end = hash->table + hash->size;
  for ( p=hash->table; p < end; p++ )
    {
      if ( p->entry.key )
	free( p->entry.key );
      if ( p->entry.data )
	free( p->entry.data );
    }
  hdestroy_r( hash );
  free( hash );
}

int
consume_line( FILE *fd )
{
  int c;
  while ( ( ( c = fgetc( fd ) ) != EOF ) && ( c != '\n' ) );
  if ( c == EOF )
    {
      fprintf( stderr, "Error: missing '\\n'\n" );
      return 0;
    }
  else
    return 0;
}

char *
strstrip( char *s )
{
  int len = strlen( s );
  char *d = calloc( len + 1, sizeof( char ) );

  char *a, *b;
  for ( a=s, b=d; *a != 0; a++ )
    {
      if ( isalnum( *a ) || ( *a == '.' ) || ( *a == ',' ) ||
	   ( *a == '(' ) || ( *a == ')' ) )
	{
	  *b = *a;
	  b++;
	}
    }
  *b = 0;

  return d;
}


int
parse_values( FILE *fd, hashtable_t *hash )
{
  int c, lastline=0;
  char buf[ 4096 ];

  /* find '{' */
  while ( ( ( c = fgetc( fd ) ) != EOF ) && ( c != '{' ) );

  while ( ( ! feof( fd ) ) && ( fgets( buf, 4095, fd ) != NULL ) &&
	  ( ! lastline ) )
    {
      if ( index( buf, '}' ) != NULL )
	lastline=1;

      char *equalsign = index( buf, '=' );

      if ( equalsign == NULL )
	continue;
      else if ( equalsign == buf )
	return -1;

      *equalsign = 0;
      equalsign ++;

      char *key = strstrip( buf );
      char *value = strstrip( equalsign );

      ENTRY e = { key, value };
      ENTRY *ep;
      hsearch_r( e, ENTER, &ep, hash );
      if ( ep == NULL )
	{
	  free( key );
	  free( value );
	  return -1;
	}
    }

  if ( lastline )
    return 0;
  else
    return -1;
}

int
get_int_value( hashtable_t *hash, char *key, int *ret )
{
  ENTRY e, *ep;

  e.key = key;
  hsearch_r( e, FIND, &ep, hash );
  if ( ep == NULL )
    {
      fprintf( stderr, "Error: missing property \"%s\"\n", key );
      return -1;
    }
  *ret = atoi( (char *)ep->data );
  return 0;
}

int
get_hex_value( hashtable_t *hash, char *key, int *ret )
{
  ENTRY e, *ep;

  e.key = key;
  hsearch_r( e, FIND, &ep, hash );
  if ( ep == NULL )
    {
      fprintf( stderr, "Error: missing property \"%s\"\n", key );
      return -1;
    }
  *ret = strtol( (char *)ep->data, NULL, 16 );
  return 0;
}


int
get_float_value( hashtable_t *hash, char *key, float *ret )
{
  ENTRY e, *ep;

  e.key = key;
  hsearch_r( e, FIND, &ep, hash );
  if ( ep == NULL )
    {
      fprintf( stderr, "Error: missing property \"%s\"\n", key );
      return -1;
    }
  *ret = atof( (char *)ep->data );
  return 0;
}

int
get_point_value( hashtable_t *hash, char *key, coord_t *ret )
{
  ENTRY e, *ep;

  e.key = key;
  hsearch_r( e, FIND, &ep, hash );
  if ( ep == NULL )
    {
      fprintf( stderr, "Error: missing property \"%s\"\n", key );
      return -1;
    }
  char *value = (char *)ep->data;
  unsigned x, y;

  if ( sscanf( value, "(%u,%u)", &x, &y ) != 2 )
    {
      fprintf( stderr, "Error: point \"%s\"in invalid format!\n", key );
      return -1;
    }

  SET_COORD( (*ret), x, y );

  return 0;
}

int
get_opt_drawopts( hashtable_t *hash, char *key, int *ret )
{
  ENTRY e, *ep;

  *ret = DRAW_NORMAL;

  e.key = key;
  hsearch_r( e, FIND, &ep, hash );
  if ( ep == NULL )
    {
      fprintf( stderr, "Warning: missing property \"%s\"\n", key );
      return 0;
    }
  char *value = (char *)ep->data;
  char *a=value, *b=value, *end = value + strlen( value );

  char buf[ 512 ] = "";
  while ( ( a < end ) && ( *a != 0 ) )
    {
      b = index( b, ',' );
      if ( b != NULL )
	*b = '\0';

      if ( sscanf( a, "%511s", buf ) != 1 )
	break;

      buf[ 511 ] = 0;

      if ( strcasecmp( buf, "normal" ) == 0 )
	*ret |= DRAW_NORMAL;
      else if ( strcasecmp( buf, "fill" ) == 0 )
	*ret |= DRAW_FILL;
      else if ( strcasecmp( buf, "antialias" ) == 0 )
	*ret |= DRAW_ANTIALIAS;
      else
	fprintf( stderr, "Warning: invalid draw option: \"%s\".\n", buf );

      if ( b == NULL )
	break;
      a = b + 1;
    }


  return 0;

}


int
set_object_values( object_t *object, hashtable_t *hash )
{
  int ret, i;
  float f;

  ret = get_int_value( hash, "depth", &i );
  if ( ret != 0 ) return ret;
  else object->depth = i;

  ret = get_hex_value( hash, "color", &i );
  if ( ret != 0 ) return ret;
  else COLOR_FROM_ARGB( object->color, i );

  ret = get_float_value( hash, "transp", &f );
  if ( ret != 0 ) return ret;
  else object->transp = f;

  ret = get_opt_drawopts( hash, "draw", &i );
  if ( ret != 0 ) return ret;
  else object->draw_opts = i;

  return 0;
}

int
set_line_values( line_t *line, hashtable_t *hash )
{
  return
    get_point_value( hash, "begin", &(line->r.p0) ) ||
    get_point_value( hash, "end", &(line->r.p1) );
}

int
create_line( FILE *fd, scene_t *scene )
{
  line_t *line = line_new();
  hashtable_t *hash = hashtable_new();
  int ret=0;

  ret = parse_values( fd, hash );

  if ( ret == 0 )
    ret = set_object_values( OBJECT( line ), hash );

  if ( ret == 0 )
    ret = set_line_values( line, hash );

  if ( ret == 0 )
    scene_add_object( scene, OBJECT( line ) );

  hashtable_free( hash );

  return 0;
}

int
set_circle_values( circle_t *circle, hashtable_t *hash )
{
  int r, i;

  r = get_int_value( hash, "radius", &i );
  if ( r != 0 ) return r;
  else circle->radius = i;

  r = get_point_value( hash, "center", &(circle->center) );
  if ( r != 0 ) return r;

  return 0;
}


int
create_circle( FILE *fd, scene_t *scene )
{
  circle_t *circle = circle_new();
  hashtable_t *hash = hashtable_new();
  int ret=0;

  ret = parse_values( fd, hash );

  if ( ret == 0 )
    ret = set_object_values( OBJECT( circle ), hash );

  if ( ret == 0 )
    ret = set_circle_values( circle, hash );

  if ( ret == 0 )
    scene_add_object( scene, OBJECT( circle ) );

  hashtable_free( hash );

  return 0;
}

int
set_ellipse_values( ellipse_t *ellipse, hashtable_t *hash )
{
  int r, i;

  r = get_int_value( hash, "radiusx", &i );
  if ( r != 0 ) return r;
  else ellipse->radiusx = i;

  r = get_int_value( hash, "radiusy", &i );
  if ( r != 0 ) return r;
  else ellipse->radiusy = i;

  r = get_point_value( hash, "center", &(ellipse->center) );
  if ( r != 0 ) return r;

  return 0;
}


int
create_ellipse( FILE *fd, scene_t *scene )
{
  ellipse_t *ellipse = ellipse_new();
  hashtable_t *hash = hashtable_new();
  int ret=0;

  ret = parse_values( fd, hash );

  if ( ret == 0 )
    ret = set_object_values( OBJECT( ellipse ), hash );

  if ( ret == 0 )
    ret = set_ellipse_values( ellipse, hash );

  if ( ret == 0 )
    scene_add_object( scene, OBJECT( ellipse ) );

  hashtable_free( hash );

  return 0;
}

int
set_polygon_values( polygon_t *polygon, hashtable_t *hash )
{
  ENTRY e, *ep;

  e.key = "vertexes";
  hsearch_r( e, FIND, &ep, hash );
  if ( ep == NULL )
    {
      fprintf( stderr, "Error: missing property \"vertexes\"\n" );
      return -1;
    }
  char *value = (char *)ep->data;
  unsigned x, y;

  while ( sscanf( value, "(%u,%u)", &x, &y ) == 2 )
    {
      polygon_add_vertex( polygon, x, y );

      value = index( value, ')' );
      if ( value == NULL )
	break;
      else
	value ++;
    }

  return 0;
}

int
create_polygon( FILE *fd, scene_t *scene )
{
  polygon_t *polygon = polygon_new();
  hashtable_t *hash = hashtable_new();
  int ret=0;

  ret = parse_values( fd, hash );

  if ( ret == 0 )
    ret = set_object_values( OBJECT( polygon ), hash );

  if ( ret == 0 )
    ret = set_polygon_values( polygon, hash );

  if ( ret == 0 )
    scene_add_object( scene, OBJECT( polygon ) );

  hashtable_free( hash );

  return 0;
}

int
screen_new( uint16_t x, uint16_t y )
{
  screen = surface_new( x, y );
  return visual_init( x, y );
}

int
screen_free( void )
{
  if ( screen != NULL )
    {
      visual_quit();
      surface_free( screen );
      screen = NULL;
    }

  return 0;
}


int
scene_size( FILE *fd, scene_t *scene )
{
  char buf[ 512 ] = "";

  if ( screen != NULL )
    {
      fprintf( stderr, "Warning: scene size already set! Ignored.\n" );
      return 0;
    }

  if ( fgets( buf, 511, fd ) == NULL )
    return -1;

  char *equalsign = index( buf, '=' );
  if ( equalsign == NULL )
    {
      fprintf( stderr, "Error: wrong \"scene_size\" specification.\n" );
      return -1;
    }
  equalsign++;

  char *value = strstrip( equalsign );
  unsigned x, y;
  int r = sscanf( value, "%u,%u", &x, &y );
  free( value );


  if ( r != 2 )
    {
      fprintf( stderr, "Error: wrong \"scene_size\" format!\n" );
      return -1;
    }

  return screen_new( x, y );
}

int
scene_show( FILE *fd, scene_t *scene )
{
  if ( consume_line( fd ) != 0 )
    return -1;

  if ( screen == NULL )
    {
      fprintf( stderr,
	       "Error: You should set \"scene_size\" before "
	       "\"scene_show\"!\n" );
      return -1;
    }

  scene_render( scene, screen );
  coord_t pos = { 0, 0 };
  visual_display( screen, pos );
  visual_update();
  return 0;
}

int
scene_clear( FILE *fd, scene_t *scene )
{
  if ( consume_line( fd ) != 0 )
    return -1;

  if ( screen == NULL )
    {
      fprintf( stderr,
	       "Error: You should set \"scene_size\" before "
	       "\"scene_show\"!\n" );
      return -1;
    }

  scene_free_objects( scene );
  surface_clear( screen );
  return visual_clear();
}


int
waitkey( FILE *fd, scene_t *scene )
{
  if ( consume_line( fd ) != 0 )
    return -1;

  visual_waitkey();
  return 0;
}


static struct parse_map
{
  char *string;
  int (*function)( FILE *fd, scene_t *scene );
} parse_map[] =
  {
    {
      .string = "scene_size",
      .function = scene_size,
    },

    {
      .string = "scene_show",
      .function = scene_show,
    },

    {
      .string = "scene_clear",
      .function = scene_clear,
    },

    {
      .string = "waitkey",
      .function = waitkey,
    },

    {
      .string = "line",
      .function = create_line,
    },

    {
      .string = "circle",
      .function = create_circle,
    },

    {
      .string = "ellipse",
      .function = create_ellipse,
    },

    {
      .string = "polygon",
      .function = create_polygon,
    },

    { NULL, NULL }, /* keep tail! */
  };


int
__parse_file( FILE *fd, scene_t *scene )
{
  char buf[ 1024 ] = "";

  while ( ! feof( fd ) )
    {
      if ( fscanf( fd, "%1023s", buf ) == EOF )
	return 0;

      struct parse_map *p;
      for ( p=parse_map; p->string != NULL; p++ )
	{
	  if ( strcmp( buf, p->string ) == 0 )
	    {
	      int ret = p->function( fd, scene );
	      if ( ret != 0 )
		  return ret;
	    }
	}
    }

  return 0;
}

int
parse_file( FILE *fd )
{
  if ( fd == NULL )
    return -1;

  scene_t *scene = scene_new();

  int ret = __parse_file( fd, scene );

  scene_free_objects( scene );
  scene_free( scene );

  screen_free();

  return ret;
}

void
usage( const char *progname )
{
  fprintf( stderr,
	   "Usage:\n"
	   "\t%s scene-files\n\n",
	   progname );
}

int
main( int argc, char *argv[] )
{
  FILE *fd;

  if ( argc > 1 )
    {
      int i;
      for ( i=1; i < argc; i++ )
	{
	  char *fname = argv[ i ];
	  if ( ( strcmp( fname, "--help" ) == 0 ) ||
	       ( strcmp( fname, "-help" ) == 0 ) ||
	       ( strcmp( fname, "-h" ) == 0 ) )
	    {
	      usage( argv[ 0 ] );
	      return 0;
	    }
	  else
	    {
	      fd = fopen( fname, "r" );
	      if ( fd == NULL )
		perror( "Could not open file" );
	      else
		{
		  int r = parse_file( fd );
		  if ( r != 0 )
		    {
		      fprintf( stderr, "Error parsing file: \"%s\"\n", fname );
		      return r;
		    }
		  fclose( fd );
		}
	    }
	}
    }
  else
    return parse_file( stdin );

  return 0;
}
